package com.example.deviceinfo;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.example.deviceinfo.Storage.SDevice.MEDIA_TYPE;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.os.StatFs;
import android.os.Build.VERSION;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.util.Log;

public class Storage {
    public static class SDevice {
        public static enum MEDIA_TYPE {
            SDCARD, DISK
        }

        private String ID = null;
        private String mountPoint = null;
        private String label = null;
        private MEDIA_TYPE mediaType = null;
        private boolean bMounted = false;

        @Override
        public boolean equals(Object o) {
            // TODO Auto-generated method stub
            try {
                return mountPoint.equals(((SDevice) o).getMountPoint());
            } catch (Exception e) {
                return false;
            }
        }

        public boolean isMounted() {
            return bMounted;
        }

        public void setMounted(boolean bMounted) {
            this.bMounted = bMounted;
        }

        public String getID() {
            return ID;
        }

        public void setID(String ID) {
            this.ID = ID;
        }

        public String getMountPoint() {
            return mountPoint;
        }

        public void setMountPoint(String mountPoint) {
            this.mountPoint = mountPoint;
        }

        public String getLabel() {
            return label;
        }

        public void setLabel(String label) {
            this.label = label;
        }

        public MEDIA_TYPE getMediaType() {
            return mediaType;
        }

        public void setMediaType(MEDIA_TYPE mediaType) {
            this.mediaType = mediaType;
        }
    }

    public interface IStorageListener {
        void onStorageMounted(SDevice device);

        void onStorageRemoved(SDevice device);
    }

    private static final String TAG = "Storage";

    public static final String ROOT_FOLDER = "";
    private static HandlerThread ht = null;
    private static String EXT_STORAGE_FOLDER = "";
    private static Context mContext = null;
    private static List<IStorageListener> listeners = new ArrayList<IStorageListener>();
    private static Cache<String, SDevice> sdevices = new Cache<String, SDevice>();

    private static final BroadcastReceiver deviceChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(final Context context, final Intent intent) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    String action = intent.getAction();
                    Log.i(TAG, "deviceChangeReceiver:" + action);
                    if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                        updateSDevices(scanSDevices());
                    } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {

                    } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {

                    } else if (action.equals(Intent.ACTION_MEDIA_REMOVED)) {
                        updateSDevices(scanSDevices());
                    }
                }
            };
            ht.postAction(runnable);
        }
    };

    private static boolean containsDevice(SDevice device, List<SDevice> devices) {
        for (SDevice _device : devices) {
            if (_device.equals(device))
                return true;
        }
        return false;
    }

    private static void updateSDevices(List<SDevice> list) {
        List<SDevice> _list = sdevices.values();
        boolean flag = false;
        if (_list.size() != list.size())
            flag = true;
        else {
            for (SDevice device1 : list) {
                boolean _flag = false;
                for (SDevice device2 : _list) {
                    if (device1.equals(device2)) {
                        _flag = true;
                        break;
                    }
                }
                if (!_flag) {
                    flag = true;
                    break;
                }
            }
        }
        Log.i(TAG, "updateSDevices flag:" + flag);
        for (SDevice device : _list)
            Log.i(TAG, "updateSDevices old:" + device.getMountPoint());
        for (SDevice device : list)
            Log.i(TAG, "updateSDevices now:" + device.getMountPoint());
        if (flag) {
            for (SDevice device : list) {
                if (!containsDevice(device, _list)) {
                    sdevices.add(device.getMountPoint(), device);
                    notifyDeviceMounted(device);
                }
            }
            for (SDevice device : _list) {
                if (!containsDevice(device, list)) {
                    sdevices.remove(device.getMountPoint());
                    notifyDeviceRemoved(device);
                }
            }
        }
    }

    private static void notifyDeviceRemoved(SDevice device) {
        // TODO Auto-generated method stub
        synchronized (listeners) {
            for (IStorageListener l : listeners)
                l.onStorageRemoved(device);
        }
    }

    private static void notifyDeviceMounted(SDevice device) {
        // TODO Auto-generated method stub
        synchronized (listeners) {
            for (IStorageListener l : listeners)
                l.onStorageMounted(device);
        }
    }

    public static synchronized void setContext(Context context) {
        if (mContext == null) {
            mContext = context;
            if (ht == null) {
                ht = new HandlerThread("StorageManager");
                ht.start();
            }
            EXT_STORAGE_FOLDER = "/." + mContext.getPackageName() + "/";

            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
            filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
            filter.addAction(Intent.ACTION_MEDIA_EJECT);
            filter.addAction(Intent.ACTION_MEDIA_REMOVED);
            filter.addDataScheme("file");
            mContext.registerReceiver(deviceChangeReceiver, filter);
        }
    }

    public static void addListener(IStorageListener listener) {
        synchronized (listeners) {
            if (!listeners.contains(listener))
                listeners.add(listener);
        }
    }

    public static void removeListener(IStorageListener listener) {
        synchronized (listeners) {
            listeners.remove(listener);
        }
    }

    public static synchronized long getSDCardFreeSpace() {
        List<SDevice> devices = sdevices.values();
        for (SDevice device : devices)
            if (device.getMediaType() == MEDIA_TYPE.SDCARD)
                return getFreeSpace(device, false);
        return 0;
    }

    public static synchronized long getInternalMemoryFreeSpace() {
        String path = getInternalMemoryPath(ROOT_FOLDER);
        return getFreeSpace(path, false);
    }

    public static synchronized String getSDevicePath(SDevice device, String folder) {
        String path = device.getMountPoint();
        if (folder.equals(ROOT_FOLDER))
            return path;
        path += "/" + folder + "/";
        File file = new File(path);
        if (!file.exists())
            file.mkdirs();
        path = file.getAbsolutePath() + "/";
        file.setReadable(true, false);
        file.setWritable(true, false);
        // chmod(path, 777);
        return path;
    }

    public static synchronized String getSDCardPath(String folder) {
        if (isSDCardAvaiable()) {
            if (Environment.getExternalStorageDirectory().canWrite()) {
                String path = Environment.getExternalStorageDirectory().getPath();
                if (folder.equals(ROOT_FOLDER))
                    return path;
                path += EXT_STORAGE_FOLDER + folder;
                File file = new File(path);
                if (!file.exists())
                    file.mkdirs();
                path = file.getAbsolutePath() + "/";
                file.setReadable(true, false);
                file.setWritable(true, false);
                // chmod(path, 777);
                return path;
            }
        }
        return null;
    }

    private static boolean isSDCardAvaiable() {
        try {
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
                return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public static synchronized String getInternalMemoryPath(String folder) {
        String path = mContext.getFilesDir().getAbsolutePath();
        chmod(path, 777);
        if (folder.equals(ROOT_FOLDER))
            return path;
        path += "/" + folder + "/";
        File file = new File(path);
        if (!file.exists())
            file.mkdirs();
        path = file.getAbsolutePath() + "/";
        file.setReadable(true, false);
        file.setWritable(true, false);
        // chmod(path, 777);
        return path;
    }

    public static List<SDevice> getSDeviceList() {
        if (sdevices.size() == 0) {
            List<SDevice> devices = scanSDevices();
            for (SDevice device : devices)
                sdevices.add(device.getMountPoint(), device);
        }
        return sdevices.values();
    }

    public static long getFreeSpace(SDevice device, boolean bwait) {
        return getFreeSpace(device.getMountPoint(), bwait);
    }

    public static long getTotalSpace(SDevice device, boolean bwait) {
        return getTotalSpace(device.getMountPoint(), bwait);
    }

    public static long getFreeSpace(String path, boolean bwait) {
        try {
            if (bwait)
                Thread.sleep(2000);
            StatFs sf = new StatFs(path);
            long blockSize = sf.getBlockSize();
            long availCount = sf.getAvailableBlocks();
            return blockSize * availCount;
        } catch (Exception e) {
            return 0;
        }
    }

    public static long getTotalSpace(String path, boolean bwait) {
        try {
            if (bwait)
                Thread.sleep(2000);
            StatFs sf = new StatFs(path);
            long blockSize = sf.getBlockSize();
            long count = sf.getBlockCount();
            return blockSize * count;
        } catch (Exception e) {
            return 0;
        }
    }

    private static synchronized List<SDevice> scanSDevices() {
        List<SDevice> list = null;
        if (VERSION.SDK_INT >= 14)
            list = upper14.getSDevices(mContext);
        else
            list = below14.getSDevices(mContext);
        return list;
    }

    private static interface IGetSDevices {
        List<SDevice> getSDevices(Context context);
    }

    private static IGetSDevices below14 = new IGetSDevices() {
        @Override
        public List<SDevice> getSDevices(Context context) {
            // TODO Auto-generated method stub
            List<SDevice> devices = new ArrayList<SDevice>();
            String sdPath = getSDCardPath(ROOT_FOLDER);
            BufferedReader bufferedReader = null;
            List<String> dev_mountStrs = null;
            try {
                // API14以下通过读取Linux的vold.fstab文件来获取SDCard信息
                bufferedReader = new BufferedReader(
                        new FileReader(Environment.getRootDirectory().getAbsoluteFile()
                                + File.separator + "etc" + File.separator + "vold.fstab"));
                dev_mountStrs = new ArrayList<String>();
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    if (line.startsWith("dev_mount")) {
                        dev_mountStrs.add(line);
                    }
                }
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            for (int i = 0; dev_mountStrs != null && i < dev_mountStrs.size(); i++) {
                SDevice device = new SDevice();
                String[] infoStr = dev_mountStrs.get(i).split(" ");
                device.setLabel(infoStr[1]);
                device.setMountPoint(infoStr[2]);
                if (sdPath != null && device.getMountPoint().equals(sdPath)) {
                    device.setMounted(true);
                    device.setMediaType(MEDIA_TYPE.SDCARD);
                } else if (device.getMountPoint().startsWith("/mnt")) {
                    File file = new File(device.getMountPoint() + File.separator + "temp");
                    if (file.exists()) {
                        device.setMounted(true);
                    } else {
                        if (file.mkdir()) {
                            file.delete();
                            device.setMounted(true);
                        } else {
                            device.setMounted(false);
                        }
                    }
                }
                devices.add(device);
            }
            return devices;
        }
    };

    private static IGetSDevices upper14 = new IGetSDevices() {
        @Override
        public List<SDevice> getSDevices(Context context) {
            String sdPath = getSDCardPath(ROOT_FOLDER);
            List<SDevice> devices = new ArrayList<SDevice>();
            StorageVolume[] storageVolumes = null;
            try {
                StorageManager storageManager = (StorageManager) context
                        .getSystemService(Context.STORAGE_SERVICE);
                Method getVolumePaths = storageManager.getClass().getMethod("getVolumeList");
                storageVolumes = (StorageVolume[]) getVolumePaths.invoke(storageManager);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (storageVolumes != null && storageVolumes.length > 0) {
                Log.d("usb", "" + storageVolumes.length);
                for (int i = 0; i < storageVolumes.length; i++) {
                    StorageVolume mStorageVolume = storageVolumes[i];
                    SDevice device = new SDevice();
                    device.setID(String.valueOf(mStorageVolume.getStorageId()));
                    device.setMountPoint(mStorageVolume.getPath());
                    device.setMounted(checkIsMounted(context, device.getMountPoint()));
                    if (device.getMountPoint().toLowerCase().contains("sdcard")
                            || (sdPath != null && device.getMountPoint().equals(sdPath))) {
                        if (!(getTotalSpace(device, false) > 0))
                            continue;
                        device.setMediaType(MEDIA_TYPE.SDCARD);
                    } else
                        device.setMediaType(MEDIA_TYPE.DISK);
                    devices.add(device);
                }
            }
            return devices;
        }

        private boolean checkIsMounted(Context context, String mountPoint) {
            if (mountPoint == null) {
                return false;
            }
            StorageManager storageManager = (StorageManager) context
                    .getSystemService(Context.STORAGE_SERVICE);
            try {
                Method getVolumeState = storageManager.getClass().getMethod("getVolumeState",
                        String.class);
                String state = (String) getVolumeState.invoke(storageManager, mountPoint);
                return Environment.MEDIA_MOUNTED.equals(state);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    };

    private static void chmod(String path, int flag) {
        try {
            String cmd = "chmod ";
            if (new File(path).isDirectory())
                cmd += "-R " + flag + " " + path;
            else
                cmd += flag + " " + path;
            Runtime.getRuntime().exec(cmd);
            Log.d("storage", "chmod cmd:" + cmd);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
